package com.yayako.driver;

import com.yayako.struc.CFG;
import com.yayako.struc.M;
import com.yayako.utils.FileUtil;

import java.util.*;

public class Driver {
    private static final String EPSILON = "ε";
    private static final String POUND_KEY = "#";
    private static final String OUTPUT_PATH =
            "D:\\Projects\\Fundamentals of Compiling\\SyntacticParser\\src\\com\\yayako\\out\\analysis_of_sequence";

    public static void analyze(M M, String omega) {
        TreeMap<String, TreeMap<String, CFG.Production>> m = M.getM();
        String S = M.getS();

        StringBuilder logger = new StringBuilder(omega.replace(" ", "")).append("：");
        StringBuilder process = new StringBuilder("stack\tinputs\taction\tleft\t→\tright\n");
        Stack<String> stack = new Stack<>();
        omega += " #";
        List<String> inputs = Arrays.asList(omega.split(" "));

        stack.push("#");
        stack.push(S);

        int i = 0;
        String top;
        String input = inputs.get(0);
        CFG.Production production;

        while (!stack.isEmpty()) {
            top = stack.peek();

            // 栈
            process.append(arrayToString(stack.toArray(), 0))
                    .append("\t")
                    // 剩余输入
                    .append(arrayToString(inputs.toArray(), i))
                    .append("\t");

            // 栈空，输入空
            if (top.equals(POUND_KEY) && input.equals(POUND_KEY)) {
                process.append("accept!\t正确结束\t \t \n");
                logger.append("分析成功\n");
                FileUtil.write2Log(logger.toString());
                break;
            }
            // 栈空，剩余输入 || 输入空，栈中内容超出2个
            else if (top.equals(POUND_KEY) || input.equals(POUND_KEY) && stack.size() > 2) {
                process.append("error\t \t \t \n");
                logger.append("分析失败\n");
                FileUtil.write2Log(logger.toString());
                break;
            }

            // 匹配终结符
            if (M.getCfg().getT().contains(top)) {
                if (top.equals(input)) {
                    process.append("pop(")
                            .append(stack.pop())
                            .append("),next(ip)\t")
                            .append(input)
                            .append("\t \t \n");
                    input = inputs.get(++i);
                    continue;
                }
                process.append("error\t \t \t \n");
                logger.append("分析失败\n");
                FileUtil.write2Log(logger.toString());
                break;
            }
            production = m.get(top).get(input);

            // null
            if (production == null) {
                process.append("error\t \t \t \n");
                logger.append("分析失败\n");
                FileUtil.write2Log(logger.toString());
                break;
            }

            // "ε"
            if (production.getProduction().equals(EPSILON)) {
                process.append("pop(")
                        .append(stack.pop())
                        .append(")")
                        .append(production)
                        .append('\n');
            }
            // 展开产生式
            else {
                process.append("pop(")
                        .append(stack.pop())
                        .append("),push(")
                        .append(production.getProduction())
                        .append(")")
                        .append(production)
                        .append("\n");

                // 产生式反转入栈
                List<String> tmp = new ArrayList<>(production.getRight());
                Collections.reverse(tmp);
                stack.addAll(tmp);
            }
        }
        process.append(" \t \t \t \t \t \n");
        FileUtil.write2Txt(OUTPUT_PATH, process.toString(), true);
    }

    private static String arrayToString(Object[] array, int start) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = start; i < array.length; ++i) {
            stringBuilder.append(array[i]);
        }
        return stringBuilder.toString();
    }
}
